﻿
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Newtonsoft.Json;
using RestSharp;

//{"createdeployment": {
//	"SubscriptionAlias":"SubscriptionA",
//	"Package":
//		{"PackageName":"AzureTableWCFDataService.Azure.cspkg", 
//		  "ConfigFileName":"ServiceConfiguration.Cloud.cscfg"},
//	"Service":
//		{"Label":"v1",
//		 "ServiceName":"golive-wcfdataservice",
//		 "Slot":"production"}
//	}
//}

namespace wamr.Worker
{
    class DeployServiceTask: ManagerTask
    {
        // input parameters
        private string _serviceName = "";
        private string _deploymentSlot = "";
        private string _packageName = "";
        private string _configFileName = "";
        private string _deploymentLabel = "";

        // retrieved from config settings
        private string _subscriptionID = "";
        private string _certificateThumbprint = "";
        private string _storageAccountConn = "";
        private string _storageAccountName = "";
        private string _containerName = "";

        // derived value
        private string _packageUrl = "";
        private string _configFileUrl = "";

        private bool storageInitialized = false;
        private CloudBlobClient cbc = null;
        private string requestID = string.Empty;
        private RestAPIHelper rest = null;

        public XNamespace wa = "http://schemas.microsoft.com/windowsazure";
        XNamespace ns1 = "http://www.w3.org/2001/XMLSchema-instance";

        public DeployServiceTask(string ManagerMessage)
        {
            Trace.TraceInformation("DeployServiceTask CTOR processing.");

            MessageIsValid = true;
            TaskType = "DeployService";

            JSONClasses.JSONCreateDeploymentRootObject r = JsonConvert.DeserializeObject<JSONClasses.JSONCreateDeploymentRootObject>(ManagerMessage);

            if (string.IsNullOrEmpty(r.createdeployment.SubscriptionAlias) ||
                string.IsNullOrEmpty(r.createdeployment.Package.ConfigFileName) ||
                string.IsNullOrEmpty(r.createdeployment.Package.PackageName) ||
                string.IsNullOrEmpty(r.createdeployment.Service.Label) ||
                string.IsNullOrEmpty(r.createdeployment.Service.ServiceName) ||
                string.IsNullOrEmpty(r.createdeployment.Service.Slot))
            {
                MessageIsValid = false;
                Trace.TraceError("All parameters are required.");
                return;
            }

            _subscriptionID = CloudConfigurationManager.GetSetting(r.createdeployment.SubscriptionAlias + ".SubscriptionID");
            _certificateThumbprint = CloudConfigurationManager.GetSetting(r.createdeployment.SubscriptionAlias + ".CertificateThumbprint");

            _serviceName = r.createdeployment.Service.ServiceName;
            _deploymentSlot = r.createdeployment.Service.Slot;
            _deploymentLabel = r.createdeployment.Service.Label;

            _packageName = r.createdeployment.Package.PackageName;
            _configFileName = r.createdeployment.Package.ConfigFileName;

            // read wamr storage account configuration settings
            // developer storage references are here to help with debugging the core logic.
            // you can only go so far with this, since Azure won't deploy a package from a 
            // storage account outside the subscription being deployed into.
            if (RoleEnvironment.IsEmulated)
            {
                _storageAccountName = "devstoreaccount1";
                _containerName = "deployments";
                _storageAccountConn = "UseDevelopmentStorage=true";
            }
            else
            {
                _storageAccountConn = CloudConfigurationManager.GetSetting(r.createdeployment.SubscriptionAlias + ".DeployConnectString");
                _storageAccountName = CloudConfigurationManager.GetSetting(r.createdeployment.SubscriptionAlias + ".DeployAccountName");
                _containerName = CloudConfigurationManager.GetSetting(r.createdeployment.SubscriptionAlias + ".DeployContainerName");
            }

            _packageUrl = GetBlobUri(_packageName).ToString();
            _configFileUrl = GetBlobUri(_configFileName).ToString();

        }

        public override bool PreConditionsMet()
        {
            TaskDisposition = TaskDispositionEnum.PROCEED;
            return true;
        }

        public override bool Initiate()
        {
            Uri uri = null;
            XDocument requestBody = null;

            try
            {
                string uriFormat = "https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}";
                uri = new Uri(string.Format(uriFormat, _subscriptionID, _serviceName, _deploymentSlot));

                // Create the request XML document
                string base64Config = GetConfigFileHelper();
                if (string.IsNullOrEmpty(base64Config))
                {
                    TaskDisposition = TaskDispositionEnum.FAIL;
                    Trace.TraceError("DeployServiceTask could not find configuration file in blob storage.");
                    return false;
                }

                requestBody = new XDocument(
                    new XDeclaration("1.0", "UTF-8", "no"),
                    new XElement(
                        wa + "CreateDeployment",
                        new XElement(wa + "Name", _serviceName),
                        new XElement(wa + "PackageUrl", _packageUrl),
                        new XElement(wa + "Label", _deploymentLabel.ToBase64()),
                        new XElement(wa + "Configuration", base64Config),
                        new XElement(wa + "StartDeployment", "true"),
                        new XElement(wa + "TreatWarningAsError", "false")));
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.Message);
                TaskDisposition = TaskDispositionEnum.FAIL;
                return false;
            }

            XDocument responseBody;
            try
            {
                rest = new RestAPIHelper(_certificateThumbprint);
            }
            catch (ArgumentException)
            {
                TaskDisposition = TaskDispositionEnum.FAIL;
                return false;
            }
            catch (Exception ex)
            {
                TaskDisposition = TaskDispositionEnum.FAIL;
                Trace.TraceError("Error occurred creating RestAPIHelper: {0}", ex.Message);
                return false;
            }

            try
            {
                requestID = rest.InvokeRequest(uri, "POST", HttpStatusCode.Accepted, requestBody, out responseBody);
            }
            catch (ApplicationException ex)
            {
                Trace.TraceError(ex.Message);
                TaskDisposition = TaskDispositionEnum.FAIL;
                return false;
            }

            return true;
        }

        public override void ResolveDependency()
        {
            // there are no dependencies for this task type so just...
            return;
        }

        public override bool Status()
        {
            if (rest == null)
            {
                TaskDisposition = TaskDispositionEnum.FAIL;
                return true;
            }

            try
            {
                string uriFormat = "https://management.core.windows.net/{0}/operations/{1}";
                Uri uri = new Uri(String.Format(uriFormat, _subscriptionID, requestID));
                XDocument responseBody;
                rest.InvokeRequest(uri, "GET", HttpStatusCode.OK, null, out responseBody);

                XElement operation = responseBody.Element(wa + "Operation");
                RestAPIHelper.OperationStatus status = (RestAPIHelper.OperationStatus)Enum.Parse(typeof(RestAPIHelper.OperationStatus), operation.Element(wa + "Status").Value);
                if (status == RestAPIHelper.OperationStatus.Succeeded)
                    TaskDisposition = TaskDispositionEnum.COMPLETE;
                else if (status == RestAPIHelper.OperationStatus.Failed || status == RestAPIHelper.OperationStatus.TimedOut)
                {
                    XElement error = operation.Element(wa + "Error");
                    if (error != null)
                    {
                        XElement code = error.Element(wa + "Code");
                        XElement message = error.Element(wa + "Message");

                        Trace.TraceError("Error status returned, code: {0}, message: {1}", code.Value, message.Value);
                    }

                    TaskDisposition = TaskDispositionEnum.FAIL;
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.Message);
                TaskDisposition = TaskDispositionEnum.FAIL;
                return false;
            }

            return true;
        }

        // Helpers beyond this point        
        private string GetConfigFileHelper()
        {
            InitializeStorageHelper();

            CloudBlockBlob c;
            try
            {
                c = (CloudBlockBlob)(cbc.GetBlobReferenceFromServer(new Uri(_configFileUrl)));
            }
            catch (StorageException ex)
            {
                Trace.TraceError("Error referencing config file blob: {0}", ex.Message);
                return null;
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error referencing config file blob: {0}", ex.Message);
                return null;
            }

            byte[] configFileBytes = null;
            using (MemoryStream m = new MemoryStream())
            {
                c.DownloadToStream(m);
                configFileBytes = m.ToArray();
            }

            if (configFileBytes[0] == 0xEF)
            {
                Array.Copy(configFileBytes, 3, configFileBytes, 0, configFileBytes.Length - 3);
                configFileBytes[configFileBytes.Length - 1] = 0x0D;
                configFileBytes[configFileBytes.Length - 2] = 0x0D;
                configFileBytes[configFileBytes.Length - 3] = 0x0D;
            }

            string configFileBase64 = Convert.ToBase64String(configFileBytes);

            return configFileBase64;

        }

        private void InitializeStorageHelper()
        {
            if (storageInitialized)
            {
                return;
            }

            try
            {
                // parse the connection string & create object
                Microsoft.WindowsAzure.Storage.CloudStorageAccount storageAccount;
                if (Microsoft.WindowsAzure.Storage.CloudStorageAccount.TryParse(_storageAccountConn, out storageAccount))
                {
                    // create the blob storage client
                    cbc = storageAccount.CreateCloudBlobClient();

                    // and the container for deployment packages
                    CloudBlobContainer container = cbc.GetContainerReference(_containerName);
                    container.CreateIfNotExists();
                }

            }
            catch (Exception ex)
            {
                Trace.TraceError(string.Format("Error initializing storage: {0}", ex.InnerException));
                return;
            }

            storageInitialized = true;
        }

        private Uri GetBlobUri(string fileName)
        {
            if (RoleEnvironment.IsEmulated)
                return new Uri(string.Format("http://127.0.0.1:10000/devstoreaccount1/{1}/{2}", _storageAccountName, _containerName, fileName));
            else
                return new Uri(string.Format("http://{0}.blob.core.windows.net/{1}/{2}", _storageAccountName, _containerName, fileName));
        }

    }
}
